#include <gtest/gtest.h>
#include <thread>
#include <vector>
#include <atomic>
#include <chrono>
#include "blocking_queue.h"

class BlockingQueueTestInt : public ::testing::Test {
protected:
    void SetUp() override {
        queue_ = BlockingQueue<int>::create(10);
    }

    void TearDown() override {
        if (queue_) {
            queue_->stop();
        }
    }

    std::shared_ptr<BlockingQueue<int>> queue_;
};

TEST_F(BlockingQueueTestInt, BasicPushPop) {
    EXPECT_TRUE(queue_->push(1));
    EXPECT_TRUE(queue_->push(2));

    int value = 0;
    EXPECT_TRUE(queue_->pop(value));
    EXPECT_EQ(1, value);

    EXPECT_TRUE(queue_->pop(value));
    EXPECT_EQ(2, value);
}

TEST_F(BlockingQueueTestInt, CapacityLimit) {
    for (int i = 0; i < 10; ++i) {
        EXPECT_TRUE(queue_->push(i));
    }

    EXPECT_FALSE(queue_->push(11, 10)); // 10ms timeout

    int value;
    EXPECT_TRUE(queue_->pop(value));

    EXPECT_TRUE(queue_->push(11));
}

TEST_F(BlockingQueueTestInt, TimeoutPush) {
    for (int i = 0; i < 10; ++i) {
        EXPECT_TRUE(queue_->push(i));
    }

    auto start = std::chrono::steady_clock::now();
    EXPECT_FALSE(queue_->push(11, 50)); // 50ms timeout
    auto end = std::chrono::steady_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    EXPECT_GE(duration.count(), 45); // at least 45ms
}

TEST_F(BlockingQueueTestInt, TimeoutPop) {
    int value = 0;
    auto start = std::chrono::steady_clock::now();
    EXPECT_FALSE(queue_->pop(value, 50)); // 50ms timeout
    auto end = std::chrono::steady_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    EXPECT_GE(duration.count(), 45); // at least 45ms
}

TEST_F(BlockingQueueTestInt, StopBehavior) {
    queue_->stop();

    EXPECT_FALSE(queue_->push(1));
    EXPECT_FALSE(queue_->push(2));

    int value = 0;
    EXPECT_FALSE(queue_->pop(value));

    auto start = std::chrono::steady_clock::now();
    EXPECT_FALSE(queue_->push(1, 1000));
    EXPECT_FALSE(queue_->pop(value, 1000));
    auto end = std::chrono::steady_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    EXPECT_LE(duration.count(), 1); // at most 1ms
}

TEST_F(BlockingQueueTestInt, SizeAndEmpty) {
    EXPECT_TRUE(queue_->empty());
    EXPECT_EQ(0, queue_->size());

    queue_->push(1);
    EXPECT_FALSE(queue_->empty());
    EXPECT_EQ(1, queue_->size());

    queue_->push(2);
    EXPECT_EQ(2, queue_->size());

    int value;
    queue_->pop(value);
    EXPECT_EQ(1, queue_->size());

    queue_->pop(value);
    EXPECT_TRUE(queue_->empty());
    EXPECT_EQ(0, queue_->size());
}

TEST_F(BlockingQueueTestInt, MultiProducerSingleConsumer) {
    const int num_producers = 4;
    const int items_per_producer = 100;
    std::vector<std::thread> producers;
    std::atomic<int> total_produced{0};
    std::atomic<int> total_consumed{0};

    for (int i = 0; i < num_producers; ++i) {
        producers.emplace_back([this, i, items_per_producer, &total_produced]() {
            for (int j = 0; j < items_per_producer; ++j) {
                if (queue_->push(i * items_per_producer + j, 1000)) {
                    total_produced++;
                }
            }
        });
    }

    std::thread consumer([this, num_producers, items_per_producer, &total_consumed]() {
        int value;
        while (total_consumed < num_producers * items_per_producer) {
            if (queue_->pop(value, 1000)) {
                total_consumed++;
            }
        }
    });

    for (auto& producer : producers) {
        producer.join();
    }

    consumer.join();

    EXPECT_EQ(num_producers * items_per_producer, total_produced);
    EXPECT_EQ(total_produced, total_consumed);
}

TEST_F(BlockingQueueTestInt, SingleProducerMultiConsumer) {
    const int num_consumers = 4;
    const int total_items = 400;
    std::vector<std::thread> consumers;
    std::atomic<int> total_consumed{0};

    for (int i = 0; i < num_consumers; ++i) {
        consumers.emplace_back([this, &total_consumed]() {
            int value;
            while (queue_->pop(value, 1000)) {
                total_consumed++;
            }
        });
    }

    std::thread producer([this, total_items]() {
        for (int i = 0; i < total_items; ++i) {
            queue_->push(i, 1000);
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        queue_->stop();
    });

    producer.join();

    for (auto& consumer : consumers) {
        consumer.join();
    }

    EXPECT_EQ(total_items, total_consumed);
}

TEST_F(BlockingQueueTestInt, CreateWithZeroSize) {
    auto zero_queue = BlockingQueue<int>::create(0);
    EXPECT_EQ(zero_queue, nullptr);
}


class BlockingQueueTestStr : public ::testing::Test {
protected:
    void SetUp() override {
        queue_ = BlockingQueue<std::string>::create(100);
    }

    void TearDown() override {
        if (queue_) {
            queue_->stop();
        }
    }

    std::shared_ptr<BlockingQueue<std::string>> queue_;
};

TEST_F(BlockingQueueTestStr, MoveSemantics) {
    std::string test_str = "test";

    EXPECT_TRUE(queue_->push(std::move(test_str)));
    EXPECT_TRUE(test_str.empty());

    std::string popped_str;
    EXPECT_TRUE(queue_->pop(popped_str));
    EXPECT_EQ(popped_str, "test");
}

TEST_F(BlockingQueueTestStr, CopySemantics) {
    std::string test_str = "test";

    EXPECT_TRUE(queue_->push(test_str));
    EXPECT_EQ(test_str, "test");

    std::string popped_str;
    EXPECT_TRUE(queue_->pop(popped_str));
    EXPECT_EQ(popped_str, test_str);
}


TEST_F(BlockingQueueTestStr, MultiThread) {
    const int test_count = 1000000;
    std::thread producer([this, test_count]() {
        for (int i = 1; i <= test_count; ++i) {
            std::string str = std::to_string(i);
            EXPECT_FALSE(str.empty());
            EXPECT_TRUE(queue_->push(std::move(str), 200));
            EXPECT_TRUE(str.empty());
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        queue_->stop();
    });
    std::thread consumer([this, test_count]() {
        std::string str;
        for (int i = 1; i <= test_count; ++i) {
            EXPECT_TRUE(queue_->pop(str, 1000));
            EXPECT_EQ(str, std::to_string(i));
        }
        EXPECT_FALSE(queue_->pop(str, 200));
    });
    producer.join();
    consumer.join();
}
